package com.yanqu.road.server.gameplayer.module.activity.travelbook;

import com.yanqu.road.dao.impl.travelbook.TravelBookUserAlbumDaoImpl;
import com.yanqu.road.dao.impl.travelbook.TravelBookUserDataDaoImpl;
import com.yanqu.road.entity.config.goods.GoodsBagInfo;
import com.yanqu.road.entity.config.goods.GoodsInfo;
import com.yanqu.road.entity.config.skill.SkillInfo;
import com.yanqu.road.entity.config.skill.SkillUpgradeInfo;
import com.yanqu.road.entity.config.travelbook.AlbumConfig;
import com.yanqu.road.entity.config.travelbook.CardConfig;
import com.yanqu.road.entity.enums.*;
import com.yanqu.road.entity.log.travelBook.LogTravelBookChoosePatron;
import com.yanqu.road.entity.log.travelBook.LogTravelBookUpgradeAlbum;
import com.yanqu.road.entity.log.travelBook.LogTravelBookUpgradeCard;
import com.yanqu.road.entity.player.UserBeauty;
import com.yanqu.road.entity.player.UserPatrons;
import com.yanqu.road.entity.travelbook.TravelBookUserData;
import com.yanqu.road.entity.travelbook.UserTravelAlbum;
import com.yanqu.road.entity.travelbook.UserTravelCard;
import com.yanqu.road.logic.config.GameConfig;
import com.yanqu.road.pb.activity.TravelBookProto;
import com.yanqu.road.server.gameplayer.GamePlayer;
import com.yanqu.road.server.gameplayer.module.base.GeneralModule;
import com.yanqu.road.server.gameplayer.module.patrons.PatronsModule;
import com.yanqu.road.server.gameplayer.module.player.BagModule;
import com.yanqu.road.server.gameplayer.module.player.BeautyModule;
import com.yanqu.road.server.gameplayer.module.player.CurrencyModule;
import com.yanqu.road.server.logic.mail.MailManager;
import com.yanqu.road.server.manger.MultipleLanguageMgr;
import com.yanqu.road.server.manger.config.*;
import com.yanqu.road.server.manger.log.AutoLogMgr;
import com.yanqu.road.server.manger.system.CommonMgr;
import com.yanqu.road.server.protocol.ClientProtocol;
import com.yanqu.road.server.protocol.GameErrorCode;
import com.yanqu.road.server.protocol.Protocol;
import com.yanqu.road.utils.date.DateHelper;
import com.yanqu.road.utils.property.Property;
import com.yanqu.road.utils.property.PropertyHelper;
import com.yanqu.road.utils.string.StringUtils;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author : Cxp
 * @Time : 2024-11-13
 * @Description : 旅行册Module
 */

public class TravelBookModule extends GeneralModule {

    public TravelBookModule(GamePlayer player) {
        super(player);
    }

    private Map<Integer, UserTravelAlbum> albumMap = new ConcurrentHashMap<>(); // 卡册Id -> 卡册
    private final Map<Integer, List<UserTravelAlbum>> beautyAlbumMap = new ConcurrentHashMap<>(); // 美女Id -> 卡册列表
    private final Map<Integer, UserTravelCard> cardMap = new ConcurrentHashMap<>(); // 卡牌Id -> 卡牌
    private TravelBookUserData userData;

    @Override
    public boolean loadData() {
        albumMap = new TravelBookUserAlbumDaoImpl().getAlbumMap(getUserId());
        userData = new TravelBookUserDataDaoImpl().getUserData(getUserId());

        for (UserTravelAlbum album : albumMap.values()) {
            List<UserTravelCard> cards = album.getCards();
            for (UserTravelCard card : cards) {
                cardMap.put(card.getCardId(), card);
            }
            int albumId = album.getAlbumId();
            AlbumConfig albumInfo = TravelBookConfigMgr.getAlbumInfo(albumId);
            if (albumInfo == null) {
                log.error("旅行册 卡册ID:{} 配置获取失败", albumId);
                continue;
            }
            int beautyId = albumInfo.getBeautyId();
            if (!beautyAlbumMap.containsKey(beautyId)) {
                beautyAlbumMap.put(beautyId, new ArrayList<>());
            }
            beautyAlbumMap.get(beautyId).add(album);
        }

        return true;
    }

    @Override
    public boolean afterLoadData() {
        return true;
    }

    @Override
    public boolean saveData() {
        for (UserTravelAlbum album : albumMap.values()) {
            if (album.isInsertOption()) {
                new TravelBookUserAlbumDaoImpl().add(album);
            } else if (album.isUpdateOption()) {
                new TravelBookUserAlbumDaoImpl().update(album);
            }
        }

        if (userData != null) {
            if (userData.isInsertOption()) {
                new TravelBookUserDataDaoImpl().add(userData);
            } else if (userData.isUpdateOption()) {
                new TravelBookUserDataDaoImpl().update(userData);
            }
        }

        return true;
    }

    @Override
    public void afterLogin() {
        initSystem();
    }

    /**
     * 系统是否初始化
     *
     * @return boolean
     */
    public boolean isSystemOpen() {
        return SystemOpenMgr.systemOpen(player, eSystemId.TravelBook.getValue());
    }

    /**
     * 初始化系统
     */
    public void initSystem() {
        if (!isSystemOpen()) {
            return;
        }

        if (userData == null) {
            if (!beautyAlbumMap.isEmpty()) { // 初始化用户数据，但是已经有旅行册数据时引导设置为已完成
                userData = new TravelBookUserData(getUserId(), 103701);
            } else {
                userData = new TravelBookUserData(getUserId(), 0);
            }
            userData.setOnceRecycle(false);
        }

        if (!userData.getOnceRecycle()) { // 进行一次性卡牌回收
            recycleCardOnce();
        }

        initTravelAlbumData();
    }

    /**
     * 系统解锁或者登陆后初始化用户旅行册数据
     */
    public synchronized void initTravelAlbumData() {
        Map<Integer, UserBeauty> userBeautyMap = player.getModule(BeautyModule.class).getUserBeautyMap();
        Map<Integer, AlbumConfig> albumConfigMap = TravelBookConfigMgr.getAlbumConfigMap();
        Map<Integer, CardConfig> cardConfigMap = TravelBookConfigMgr.getCardConfigMap();

        for (AlbumConfig albumConfig : albumConfigMap.values()) {
            int albumId = albumConfig.getAlbumId();
            int confBeautyId = albumConfig.getBeautyId();
            // 如果用户拥有某个美女却缺少相关的卡册，将缺失的卡册初始化
            if (!albumMap.containsKey(albumId) && userBeautyMap.containsKey(confBeautyId)) {
                List<UserTravelCard> cards = new ArrayList<>();
                for (CardConfig cardConfig : cardConfigMap.values()) {
                    int cardId = cardConfig.getCardId();
                    if (cardConfig.getAlbumId() == albumId) {
                        UserTravelCard card = new UserTravelCard(cardId, 0, 0, 0);
                        cards.add(card);
                        cardMap.put(cardId, card);
                    }
                }
                UserTravelAlbum album = new UserTravelAlbum(player.getUserId(), albumId, confBeautyId, 0, cards);
                albumMap.put(albumId, album);

                if (!beautyAlbumMap.containsKey(confBeautyId)) {
                    beautyAlbumMap.put(confBeautyId, new ArrayList<>());
                }
                beautyAlbumMap.get(confBeautyId).add(album);
            }
        }

        syncUserData(new ArrayList<>(albumMap.values()));
    }

    /**
     * 美女解锁后初始化旅行册数据
     *
     * @param beautyId 美女ID
     */
    public synchronized void initBeautyAlbumData(int beautyId) {
        AlbumConfig albumConfig = TravelBookConfigMgr.getBeautyAlbumConfigInfo(beautyId);

        if (albumConfig == null) {
            return;
        }

        int albumId = albumConfig.getAlbumId();
        int confBeautyId = albumConfig.getBeautyId();
        // 如果用户拥有某个美女却缺少相关的卡册，将缺失的卡册初始化
        if (!albumMap.containsKey(albumId) && beautyId == confBeautyId) {
            Map<Integer, CardConfig> cardConfigMap = TravelBookConfigMgr.getCardConfigMap();
            List<UserTravelCard> cards = new ArrayList<>();
            for (CardConfig cardConfig : cardConfigMap.values()) {
                int cardId = cardConfig.getCardId();
                if (cardConfig.getAlbumId() == albumId) {
                    UserTravelCard card = new UserTravelCard(cardId, 0, 0, 0);
                    cards.add(card);
                    cardMap.put(cardId, card);
                }
            }
            UserTravelAlbum album = new UserTravelAlbum(player.getUserId(), albumId, confBeautyId, 0, cards);
            albumMap.put(albumId, album);

            if (!beautyAlbumMap.containsKey(beautyId)) {
                beautyAlbumMap.put(beautyId, new ArrayList<>());
            }
            beautyAlbumMap.get(beautyId).add(album);
        }

        if (beautyAlbumMap.containsKey(beautyId)) {
            syncUserData(new ArrayList<>(beautyAlbumMap.get(beautyId)));
        }
    }

    /**
     * 同步用户旅行册数据
     *
     * @param albums 旅行册
     */
    private void syncUserData(List<UserTravelAlbum> albums) {
        if (albums == null) {
            return;
        }
        TravelBookProto.TravelBookUserDataSync.Builder builder = TravelBookProto.TravelBookUserDataSync.newBuilder();
        builder.setUserId(player.getUserId());
        for (UserTravelAlbum album : albums) {
            builder.addAlbum(TravelBookPb.parseAlbum(album));
        }
        if (userData != null) {
            builder.setGuideIdx(userData.getGuideIdx());
        } else {
            builder.setGuideIdx(0);
        }

        player.sendPacket(Protocol.U_TRAVEL_BOOK_USER_DATA_SYNC, builder);
    }

    /**
     * 升级卡册
     *
     * @param userId  用户ID
     * @param albumId 卡册ID
     * @return ret
     */
    public int upgradeAlbum(long userId, int albumId) {
        int check = checkUser(userId);
        if (check != 0) {
            return check;
        }

        if (!albumMap.containsKey(albumId)) {
            return GameErrorCode.E_TRAVEL_BOOK_ALBUM_NOT_EXIST;
        }

        UserTravelAlbum album = albumMap.get(albumId);
        AlbumConfig albumConfig = TravelBookConfigMgr.getAlbumInfo(albumId);
        if (albumConfig == null) {
            return GameErrorCode.E_TRAVEL_BOOK_ALBUM_CONFIG_ERROR;
        }

        BeautyModule beautyModule = player.getModule(BeautyModule.class);
        UserBeauty userBeauty = beautyModule.getUserBeauty(album.getBeautyId());
        if (userBeauty == null) {
            return GameErrorCode.E_BEAUTY_NO_FOUND;
        }

        List<Map<Integer, Integer>> upgradeMap = PatronsMgr.getAlbumUpgradeMap().get(albumId);
        if (upgradeMap == null) {
            return GameErrorCode.E_TRAVEL_BOOK_ALBUM_CONFIG_ERROR;
        }
        int level = album.getLevel();
        if (level >= upgradeMap.size()) {
            return GameErrorCode.E_TRAVEL_BOOK_ALBUM_MAX_LEVEL;
        }
        List<UserTravelCard> cards = album.getCards();
        Map<Integer, Integer> conditionMap = TravelBookConfigMgr.getAlbumUpgradeCondition(albumId, level);
        if (conditionMap == null) {
            return GameErrorCode.E_TRAVEL_BOOK_ALBUM_UPGRADE_CONDITION_ERROR;
        }

        int lvBefore = album.getLevel(); // 日志 升级前等级
        for (Map.Entry<Integer, Integer> condition : conditionMap.entrySet()) {
            int skillId = condition.getKey();
            int cardLevel = condition.getValue();
            for (UserTravelCard card : cards) { // 卡册下的每张卡牌都要满足条件
                CardConfig cardConfig = TravelBookConfigMgr.getCardInfo(card.getCardId());
                if (cardConfig == null) {
                    return GameErrorCode.E_TRAVEL_BOOK_CARD_CONFIG_ERROR;
                }
                int cardSkillId = cardConfig.getSkillInfo().getId();
                if (cardSkillId == skillId && card.getLevel() < cardLevel) {
                    return GameErrorCode.E_TRAVEL_BOOK_ALBUM_UPGRADE_CONDITION_NOT_ENOUGH;
                }
            }
        }
        album.setLevel(album.getLevel() + 1);

        // 初始化美女的旅行册卡册技能
        int skillId = albumConfig.getSkillInfo().getId();
        if (!userBeauty.getAlbumSkillMap().containsKey(skillId)) {
            userBeauty.albumSkillLevelUp(skillId, 1);
        }
        beautyModule.syncUserBeauty(new ArrayList<>(Collections.singletonList(userBeauty)), false);

        TravelBookProto.TravelBookUpgradeAlbumResp.Builder resp =
                TravelBookProto.TravelBookUpgradeAlbumResp.newBuilder();
        resp.setRet(0);
        resp.setAlbum(TravelBookPb.parseAlbum(album));
        player.sendPacket(ClientProtocol.U_TRAVEL_BOOK_UPGRADE_ALBUM, resp);

        AutoLogMgr.add(new LogTravelBookUpgradeAlbum(userId, albumId, lvBefore, album.getLevel(), System.currentTimeMillis()));

        return 0;
    }

    /**
     * 升级卡牌
     *
     * @param userId 用户ID
     * @param cardId 卡牌ID
     * @param cnt    升级次数
     * @return ret
     */
    public int upgradeCard(long userId, int cardId, int cnt) {
        int check = checkUser(userId);
        if (check != 0) {
            return check;
        }

        if (!cardMap.containsKey(cardId)) {
            return GameErrorCode.E_TRAVEL_BOOK_CARD_NOT_EXIST;
        }
        UserTravelCard card = cardMap.get(cardId);
        CardConfig cardConfig = TravelBookConfigMgr.getCardInfo(cardId);
        if (cardConfig == null) {
            return GameErrorCode.E_TRAVEL_BOOK_CARD_CONFIG_ERROR;
        }
        BeautyModule beautyModule = player.getModule(BeautyModule.class);
        UserBeauty userBeauty = beautyModule.getUserBeauty(cardConfig.getBeautyId());
        if (userBeauty == null) {
            return GameErrorCode.E_BEAUTY_NO_FOUND;
        }
        if (card.getLevel() >= cardConfig.getSkillInfo().getMaxLevel()) {
            return GameErrorCode.E_TRAVEL_BOOK_CARD_MAX_LEVEL;
        }

        SkillInfo skillInfo = cardConfig.getSkillInfo();
        int maxLevel = skillInfo.getMaxLevel();
        int upgradeType = skillInfo.getUpgradeType(0);
        int level = 0; // 需要提升的等级
        int lvBefore = card.getLevel(); // 日志 升级前等级
        Property totalCost = new Property();
        for (int i = 0; i < cnt; i++) {
            if (card.getLevel() + level == maxLevel) {
                break;
            }
            SkillUpgradeInfo upgradeInfo = SkillMgr.getSkillUpgradeInfo(upgradeType, card.getLevel() + level);
            if (null == upgradeInfo) {
                return GameErrorCode.E_TRAVEL_BOOK_CARD_UPGRADE_CONDITION_ERROR;
            }

            int num = (int) upgradeInfo.getConsumeCount();
            totalCost.addProperty(new Property(cardId, num));
            level++;
        }

        if (!player.getModule(CurrencyModule.class).removeCurrency(totalCost, eLogMoneyType.TravelBook,
                eLogMoneyType.TravelBookCardUpgrade)) {
            return GameErrorCode.E_BAG_ITEM_NO_ENOUGH;
        }

        card.setLevel(card.getLevel() + level);
        PatronsModule patronsModule = player.getModule(PatronsModule.class);

        // 更新门客或者美女的属性
        if (skillInfo.getType() == eSkillType.TravelCardEarnSpeedFixedValue.getValue()) {
            patronsModule.syncPatronsCacheByPatronsIds(new ArrayList<>(userBeauty.getOriRelationPatronsId()));
        } else if (skillInfo.getType() == eSkillType.TravelCardBeautyIntimacy.getValue()) {
            int oldIntimacy = userBeauty.getTotalIntimacy();
            userBeauty.setTravelBookIntimacy(getTotalAttr(userBeauty.getBeautyId(), skillInfo.getType()));
            beautyModule.intimacyChange(userBeauty, oldIntimacy, userBeauty.getTotalIntimacy() - oldIntimacy, eLogMoneyType.TravelBook.getValue(),
                    eLogMoneyType.TravelBookCardUpgrade.getValue(), true);
            beautyModule.syncUserBeauty(new ArrayList<>(Collections.singletonList(userBeauty)), false);
            if (card.getPatronId() != 0) {
                patronsModule.syncPatronsCache(card.getPatronId());
            }
        } else if (skillInfo.getType() == eSkillType.TravelCardBeautyGlamour.getValue()) {
            int oldGlamour = userBeauty.getTotalGlamour();
            userBeauty.setTravelBookGlamour(getTotalAttr(userBeauty.getBeautyId(), skillInfo.getType()));
            beautyModule.glamourChange(userBeauty, oldGlamour, userBeauty.getTotalGlamour() - oldGlamour, eLogMoneyType.TravelBook.getValue(),
                    eLogMoneyType.TravelBookCardUpgrade.getValue(), true);
            beautyModule.syncUserBeauty(new ArrayList<>(Collections.singletonList(userBeauty)), false);
            if (card.getPatronId() != 0) {
                patronsModule.syncPatronsCache(card.getPatronId());
            }
        } else if (skillInfo.getType() == eSkillType.TravelCardEarnSpeedQuality.getValue() && card.getPatronId() != 0) { // 升级时有绑定门客才更新门客属性
            patronsModule.syncPatronsCache(card.getPatronId());
        }

        UserTravelAlbum album = albumMap.get(cardConfig.getAlbumId());
        album.setUpdateOption();

        TravelBookProto.TravelBookUpgradeCardResp.Builder resp = TravelBookProto.TravelBookUpgradeCardResp.newBuilder();
        resp.setRet(0);
        resp.setCard(TravelBookPb.parseCard(card));
        player.sendPacket(ClientProtocol.U_TRAVEL_BOOK_UPGRADE_CARD, resp);

        AutoLogMgr.add(new LogTravelBookUpgradeCard(userId, cardId, lvBefore, card.getLevel(), System.currentTimeMillis()));

        return 0;
    }

    /**
     * 获取总属性值用于全量更新
     *
     * @param beautyId  美女ID
     * @param skillType 技能类型
     * @return intimacy or glamour
     */
    private int getTotalAttr(int beautyId, int skillType) {
        int totalAttr = 0;

        List<UserTravelAlbum> userTravelAlbums = beautyAlbumMap.get(beautyId);
        for (UserTravelAlbum album : userTravelAlbums) {
            for (UserTravelCard card : album.getCards()) {
                CardConfig cardInfo = TravelBookConfigMgr.getCardInfo(card.getCardId());
                if (cardInfo == null) {
                    continue;
                }
                SkillInfo skillInfo = cardInfo.getSkillInfo();
                if (skillInfo == null) {
                    continue;
                }
                if (skillInfo.getType() == skillType) {
                    totalAttr += SkillMgr.getSkillAddition(skillInfo, card.getLevel());
                }
            }
        }

        return totalAttr;
    }

    /**
     * 无双卡牌选择门客
     *
     * @param userId   用户ID
     * @param cardId   卡牌ID
     * @param patronId 门客ID
     * @return ret
     */
    public int choosePatron(long userId, int cardId, int patronId) {
        int check = checkUser(userId);
        if (check != 0) {
            return check;
        }

        if (!cardMap.containsKey(cardId)) {
            return GameErrorCode.E_TRAVEL_BOOK_CARD_NOT_EXIST;
        }

        UserTravelCard card = cardMap.get(cardId);
        if (card.getLevel() == 0) {
            return GameErrorCode.E_TRAVEL_BOOK_CARD_UNLOCK;
        }

        CardConfig cardConfig = TravelBookConfigMgr.getCardInfo(cardId);
        if (cardConfig == null) {
            return GameErrorCode.E_TRAVEL_BOOK_CARD_CONFIG_ERROR;
        }
        UserBeauty beauty = player.getModule(BeautyModule.class).getUserBeauty(cardConfig.getBeautyId());

        if (beauty == null) {
            return GameErrorCode.E_BEAUTY_NO_FOUND;
        }

        // cd期间允许操作
        int now = DateHelper.getCurrentSecond();
        if (card.getCd() > 0) { // 有cd
            if (now < card.getCd()) { // cd时间未到
                return GameErrorCode.E_TRAVEL_BOOK_CARD_CD;
            }
        }

        PatronsModule patronsModule = player.getModule(PatronsModule.class);
        UserPatrons newPatron = patronsModule.getUserPatrons(patronId);
        if (newPatron == null) {
            return GameErrorCode.E_PATRONS_NO_FOUND;
        }

        if (!BeautyMgr.beautyAndPatronsHasRelation(beauty, newPatron)) {
            return GameErrorCode.E_TRAVEL_BOOK_BEAUTY_PARTON_NO_RELATION;
        }

        // 新绑定门客或者换绑定门客刷新门客属性
        List<UserPatrons> patrons = new ArrayList<>();
        patrons.add(newPatron);
        UserPatrons oldPatron;
        if (card.getPatronId() != 0) {
            oldPatron = patronsModule.getUserPatrons(card.getPatronId());
            if (oldPatron != null) {
                patrons.add(oldPatron);
            }
        }
        card.setPatronId(patronId);
        card.setCd(now + GameConfig.TRAVEL_RESET_CD * 3600L);
        UserTravelAlbum album = albumMap.get(cardConfig.getAlbumId());
        album.setUpdateOption();
        patronsModule.syncPatronsCache(patrons);

        TravelBookProto.TravelBookChoosePatronResp.Builder resp =
                TravelBookProto.TravelBookChoosePatronResp.newBuilder();
        resp.setRet(0);
        resp.setCard(TravelBookPb.parseCard(card));
        player.sendPacket(ClientProtocol.U_TRAVEL_BOOK_CHOOSE_PATRON, resp);

        AutoLogMgr.add(new LogTravelBookChoosePatron(userId, cardId, patronId, System.currentTimeMillis()));

        return 0;
    }

    /**
     * 更新旅行册引导
     *
     * @param guideIdx 引导索引
     * @return ret
     */
    public int updateGuideIdx(int guideIdx) {
        userData.setGuideIdx(guideIdx);

        TravelBookProto.TravelBookUpdateGuideIdxResp.Builder resp =
                TravelBookProto.TravelBookUpdateGuideIdxResp.newBuilder();
        resp.setRet(0);
        player.sendPacket(ClientProtocol.U_TRAVEL_BOOK_UPDATE_GUIDE_IDX, resp);

        return 0;
    }

    /**
     * 校验用户信息
     *
     * @param userId 用户ID
     * @return ret
     */
    private int checkUser(long userId) {
        if (userId != player.getUserId()) {
            return GameErrorCode.E_TRAVEL_BOOK_USER_INFO_ERROR;
        }

        return 0;
    }

    public Map<Integer, List<UserTravelAlbum>> getBeautyAlbumMap() {
        return beautyAlbumMap;
    }

    public Map<Integer, UserTravelAlbum> getAlbumMap() {
        return albumMap;
    }

    /**
     * 已经拥有的道具是否能将将卡牌升到满级
     *
     * @param cardId 卡牌ID
     * @param hasGet 在重复开启随机包的过程中已经获得的道具
     * @return boolean
     */
    public boolean checkCardGetMaxLevel(int cardId, Property hasGet) {
        return getCardRestNumGetMaxLevel(cardId, hasGet) <= 0;
    }

    /**
     * 校验还需要多少包能把卡册升满级
     *
     * @param packId 卡包ID
     * @return 卡册到达满级还需要消耗的卡包数量
     */
    public int getAlbumRestNumGetMaxLevel(int packId) {
        List<GoodsBagInfo> goodsBagInfoList = GoodsBagMgr.getGoodsBagInfoList(packId);
        if (goodsBagInfoList.isEmpty()) {
            goodsBagInfoList = CommonMgr.getGoodsBagInfoList(packId);
        }

        int res = 0;
        for (GoodsBagInfo cardInfo : goodsBagInfoList) {
            int cardId = cardInfo.getGoodsId();
            int rest = getCardRestNumGetMaxLevel(cardId, new Property());
            res += Math.max(rest, 0);
        }

        return res;
    }

    /**
     * 校验还需要多少包能把卡牌升满级
     *
     * @param cardId 卡牌ID
     * @param hasGet 在重复开启随机包的过程中已经获得的道具
     * @return 卡牌到达满级还需要消耗的卡包数量
     */
    public int getCardRestNumGetMaxLevel(int cardId, Property hasGet) {
        BagModule bagModule = player.getModule(BagModule.class);
        long bagNum = bagModule.getUserBagItemCount(cardId);

        CardConfig cardConfig = TravelBookConfigMgr.getCardInfo(cardId);
        if (cardConfig == null) {
            return -1;
        }

        int curLv = 0;
        UserTravelCard card = cardMap.get(cardId);
        if (card != null) { // 可能卡牌还没有解锁生成数据
            curLv = card.getLevel();
        }

        // 获取每个卡牌升到满级需要的数量
        SkillInfo skillInfo = cardConfig.getSkillInfo();
        int upgradeType = skillInfo.getUpgradeType(0);
        long needNum = SkillMgr.getTotalConsumeOfSkillUpgradeMaxLevel(upgradeType, curLv);

        // 当前等级升到满级需要的数量 - 背包已经有的数量 - 在重复开启随机包的过程中已经获得的数量
        return (int) (needNum - bagNum - hasGet.getGoods().getOrDefault(cardId, BigInteger.valueOf(0)).intValue());
    }

    /**
     * 回收卡包
     *
     * @param packs 回收卡包列表
     * @return ret
     */
    public int recyclePack(List<TravelBookProto.TravelBookPackTemp> packs) {
        Property recycleItems = new Property();
        // 卡包里的卡牌是否满级 或者 卡册是否满级
        int randomCnt = 0; // 随机卡包数量
        int optionalCnt = 0; // 自选卡包数量
        for (TravelBookProto.TravelBookPackTemp pack : packs) {
            int restToMaxLv = getAlbumRestNumGetMaxLevel(pack.getPackId());
            if (restToMaxLv > 0) {
                return GameErrorCode.E_TRAVEL_BOOK_CAN_NOT_RECYCLE_PACK;
            }
            GoodsInfo goodsInfo = GoodsMgr.getGoodsById(pack.getPackId());
            if (goodsInfo.getType() == eGoodsType.TravelBookRandomPack.getValue()) {
                randomCnt += pack.getCount();
            } else if (goodsInfo.getType() == eGoodsType.TravelBookOptionalPack.getValue()) {
                optionalCnt += pack.getCount();
            }

            recycleItems.addProperty(new Property(pack.getPackId(), pack.getCount()));
        }

        // 校验数量 先扣道具
        CurrencyModule currencyModule = player.getModule(CurrencyModule.class);
        if (!currencyModule.removeCurrency(recycleItems, eLogMoneyType.TravelBook, eLogMoneyType.TravelBookPackRecycleDeduct)) {
            return GameErrorCode.E_BAG_ITEM_NO_ENOUGH;
        }

        // 回收奖励的配置
        String packRecycleReward = GameConfig.TRAVEL_CARD_PACKAGE_RECYCLE;
        String optionalPackRecycleReward = GameConfig.TRAVEL_CARD_SELECT_PACKAGE_RECYCLE;

        // 添加回收奖励
        Property reward = new Property(PropertyHelper.parseStringToProperty(packRecycleReward)).goodCountMultiNum(randomCnt);
        reward.addProperty(new Property(PropertyHelper.parseStringToProperty(optionalPackRecycleReward)).goodCountMultiNum(optionalCnt));
        player.getModule(CurrencyModule.class).addCurrency(reward, eLogMoneyType.TravelBook, eLogMoneyType.TravelBookPackRecycleGet);

        TravelBookProto.TravelBookRecyclePackResp.Builder resp = TravelBookProto.TravelBookRecyclePackResp.newBuilder();
        resp.setRet(0);
        resp.setReward(reward.toHelperString());
        player.sendPacket(ClientProtocol.U_TRAVEL_BOOK_RECYCLE_PACK, resp);

        return 0;
    }

    /**
     * 一次性回收卡牌
     */
    public void recycleCardOnce() {
        Map<Integer, CardConfig> cardConfigMap = TravelBookConfigMgr.getCardConfigMap();
        CurrencyModule currencyModule = player.getModule(CurrencyModule.class);

        for (CardConfig cardConfig : cardConfigMap.values()) {
            int cardId = cardConfig.getCardId();
            int cardRestNumGetMaxLevel = getCardRestNumGetMaxLevel(cardId, new Property());
            int quality = cardConfig.getQuality();
            if (cardRestNumGetMaxLevel < 0) {
                int cnt = -cardRestNumGetMaxLevel; // 需要回收的数量
                // 校验数量 先扣道具
                Property recycleItems = new Property(cardId, cnt);
                if (!currencyModule.removeCurrency(recycleItems, eLogMoneyType.TravelBook, eLogMoneyType.TravelBookCardRecycleDeduct)) {
                    continue;
                }

                // 回收奖励
                String recycleRewardStr = TravelBookConfigMgr.getRecycleRewardByQuality(quality);
                if (recycleRewardStr == null) {
                    log.error("quality:{} 没有对应品质的回收奖励配置", quality);
                    continue;
                }
                Property recycleReward =
                        new Property(PropertyHelper.parseStringToProperty(recycleRewardStr)).goodCountMultiNum(cnt);

                // 奖励发到邮件里
                String language = player.getLanguage();
                GoodsInfo goodsInfo = GoodsMgr.getGoodsById(cardId);
                String cardName = MultipleLanguageMgr.getContent(goodsInfo.getGoodsName(), language);
                String title = MultipleLanguageMgr.getContent(MailManager.TRAVEL_CARD_RECYCLE_TITLE, language);
                String content = StringUtils.format(MultipleLanguageMgr.getContent(MailManager.TRAVEL_CARD_RECYCLE_CONTENT, language),
                        cardName, cnt);
                MailManager.sendMail(player.getUserId(), eMailType.SYSTEM.getValue(), PropertyHelper.parsePropertyToString(recycleReward), content,
                        title);
            }
        }

        userData.setOnceRecycle(true);
    }
}
